// The NSpring Framework for .NET
// Copyright (c) 2003, Jeffrey Varszegi
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// - Redistributions of source code must retain the above copyright notice, this 
// list of conditions and the following disclaimer.
// 
// - Redistributions in binary form must reproduce the above copyright notice, this 
// list of conditions and the following disclaimer in the documentation and/or other 
// materials provided with the distribution.
// 
// - Neither the name of the NSpring project nor the names of its contributors may 
// be used to endorse or promote products derived from this software without 
// specific prior written permission from the copyright owner.
// 
// - No product derived from this software may be called "NSpring", nor may 
// "NSpring" appear in the name of such a product, without specific prior written 
// permission from the copyright owner.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE. 

namespace NSpring.Logging {
    
using System;
using System.Collections;
using System.Text;

/// <summary>
/// <para>
/// Each Level instance represents a standard logging level.  Each Event instance
/// has an associated Level; loggers may filter output based on the level, and differences
/// in Levels attached to Events may also be reflected in differing log output.
/// Each Level has a corresponding name and integer number (position or priority); the number is used to compare
/// levels.  Setting the Level property of a Logger instance automatically enables logging
/// of records with that level and all higher levels.
/// </para>
/// <para>
/// These are the predefined levels in ascending order: 
/// <code>
/// Debug       (priority 1)
/// Verbose     (priority 8)
/// Config      (priority 64)
/// Info        (priority 512)
/// Warning     (priority 4096)
/// Exception   (priority 32768)
/// </code>
/// </para>
/// <para>
/// The spacing of the built-in levels allows for interpolation of user-defined levels if desired, as well 
/// as comparison using a masking approach (such as that used in the NSpring.Logging.Filters.LevelFilter class).
/// </para>
/// <para>
/// In most cases, the predefined levels will be sufficient, but additional levels may optionally
/// be defined simply by creating an instance using CreateLevel().  The only 
/// restrictions are that no two levels (process-wide) may have the same name or priority.
/// </para>
/// </summary>
public sealed class Level : IComparable {
    private const string ACCEPTABLE_NAME_CHARACTERS     = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
    
    private static Hashtable levelPrioritiesMap         = new Hashtable(50);
    private static Hashtable levelNamesMap              = new Hashtable(50);
    private static Hashtable levelIDsMap                = new Hashtable(50);
    private static ArrayList levelsList                 = new ArrayList(50);

    private const string BLANK_STRING                   = "";
    
    private readonly static object staticMonitor        = new object();
    
    /// <summary>
    /// A log level representing detailed information useful for debugging
    /// </summary>
    public readonly static Level Debug                  = new Level(1, 1, "Debug", "debug", "DEBUG", true);

    /// <summary>
    /// A log level representing detailed information
    /// </summary>
    public readonly static Level Verbose                = new Level(2, 8, "Verbose", "verbose", "VERBOSE", true);

    /// <summary>
    /// A log level representing configuration information
    /// </summary>
    public readonly static Level Config                 = new Level(3, 64, "Config", "config", "CONFIG", true);

    /// <summary>
    /// A log level representing standard informational messages
    /// </summary>
    public readonly static Level Info                   = new Level(4, 512, "Info", "info", "INFO", true);

    /// <summary>
    /// A log level representing warning information
    /// </summary>
    public readonly static Level Warning                = new Level(5, 4096, "Warning", "warning", "WARNING", true);

    /// <summary>
    /// A log level representing errors and other exceptions
    /// </summary>
    public readonly static Level Exception              = new Level(6, 32768, "Exception", "exception", "EXCEPTION", true);
    
    /// <summary>
    /// A convenience log level representing the logging of all messages
    /// </summary>
    public readonly static Level All                    = new Level(7, Int32.MinValue, "All", "all", "ALL", false);
    
    /// <summary>
    /// A convenience log level representing the logging of no messages whatsoever.
    /// A logger set to this level will not log
    /// </summary>
    public readonly static Level None                   = new Level(8, Int32.MaxValue, "None", "none", "NONE", false);
    
    private static int nextID                           = 9;
    
    /// <summary>
    /// Returns the log level with the given name, if it exists.
    /// </summary>
    public static Level GetLevel(string name) {
        return (Level)levelNamesMap[name.ToLower()];   
    }
    
    /// <summary>
    /// Returns the log level corresponding to the given integer, if it exists
    /// </summary>
    public static Level GetLevel(int priority) {
        return (Level)levelPrioritiesMap[priority];   
    }
    
    /// <summary>
    /// The integer ID for this level; useful for database operations, etc.
    /// </summary>
    public int ID {
        get {
            return id;
        }
        set {
            if (IsPredefined) {
                throw new ApplicationException("Cannot set the ID of a predefined level");
            }
            else {
                lock (staticMonitor) {
                    if (levelIDsMap.ContainsKey(value)) {
                        throw new ApplicationException("ID " + Priority + " is already in use");
                    }
                    levelIDsMap.Remove(id);
                    id = value;
                    idString = id.ToString();
                    levelIDsMap[id] = this;
                    if (value >= nextID) {
                        nextID = value + 1;
                    }
                }
            }
        }
    }

    public string IDString {
        get {
            return idString;
        }
    }
    
    /// <summary>
    /// The integer corresponding to this Level instance; each is unique
    /// </summary>
    public readonly int Priority;
    
    /// <summary>
    /// A string version of the priority
    /// </summary>
    public readonly string PriorityString;

    /// <summary>
    /// The name corresponding to this Level instance; each is unique
    /// </summary>
    public readonly string Name;

    /// <summary>
    /// The name corresponding to this Level instance, in lower case; each is unique
    /// </summary>
    public readonly string NameLowerCase;

    /// <summary>
    /// The name corresponding to this Level instance, in upper case; each is unique
    /// </summary>
    public readonly string NameUpperCase;

    /// <summary>
    /// Indicates whether or not this Level is one of the predefined levelPositions,
    /// such as Debug and Exception, or is user-defined
    /// </summary>
    public readonly bool IsPredefined;

    private int id;
    private string idString;
        
    private Level() {}
    
    /// <summary>
    /// Creates and returns a new level
    /// </summary>
    /// <param name="_priority">The integer priority to be assigned to the new level; must be unique</param>
    /// <param name="_name">The name to be assigned to the new level; must be unique</param>
    /// <returns>A new Level instance</returns>
    public static Level CreateLevel(int _priority, string _name) {
        if (_name == null) {
            throw new ArgumentNullException();
        }
        Level level;
        lock (staticMonitor) {
            level = new Level(_priority, _name);
            AddLevel(level);
        }
        return level;
    }
    
    
    /// <summary>
    /// Creates and returns a new level
    /// </summary>
    /// <param name="_priority">The integer priority to be assigned to the new level; must be unique</param>
    /// <param name="_name">The name to be assigned to the new level; must be unique</param>
    /// <param name="_name">The ID to be assigned to the new level (used in database operations, etc.); must be unique</param>
    /// <returns>A new Level instance</returns>
    public static Level CreateLevel(int _priority, string _name, int _id) {
        if (_name == null) {
            throw new ArgumentNullException();
        }
        Level level;
        lock (staticMonitor) {
            level = new Level(_priority, _name, _id);
            AddLevel(level);
        }
        return level;
    }
    
    private static Hashtable CopyHashtable(Hashtable h) {
        Hashtable copy = new Hashtable(h.Count * 2);
        IEnumerator e = h.Keys.GetEnumerator();
        object key;
        while (e.MoveNext()) {
            key = e.Current;
            copy[key] = h[key];
        }
        return copy;
    }

    private static ArrayList CopyArrayList(ArrayList al) {
        ArrayList copy = new ArrayList(al.Count + 1);
        IEnumerator e = al.GetEnumerator();
        while (e.MoveNext()) {
            copy.Add(e.Current);
        }
        return copy;
    }
    
    private static void ValidateLevel(Level level) {
        if (levelPrioritiesMap.ContainsKey(level.Priority)) {
            throw new ApplicationException("A level already exists with the specified priority (" + level.Priority + ")");
        }
        if (levelNamesMap.ContainsKey(level.NameLowerCase)) {
            throw new ApplicationException("A level already exists with the specified name (" + level.NameLowerCase + ")");
        }
        if (levelIDsMap.ContainsKey(level.ID)) {
            throw new ApplicationException("A level already exists with the specified ID (" + level.ID + ")");
        }
    }
    
    private static void AddLevel(Level level) {
        lock (staticMonitor) {
            ValidateLevel(level);

            Hashtable newLevelPrioritiesMap = CopyHashtable(levelPrioritiesMap);
            Hashtable newLevelNamesMap = CopyHashtable(levelNamesMap);
            Hashtable newLevelIDsMap = CopyHashtable(levelIDsMap);
            ArrayList newLevelsList = CopyArrayList(levelsList);
            
            newLevelPrioritiesMap.Add(level.Priority, level);
            newLevelNamesMap.Add(level.NameLowerCase, level);
            newLevelIDsMap.Add(level.ID, level);
            newLevelsList.Add(level);
            newLevelsList.Sort();
            
            levelPrioritiesMap = newLevelPrioritiesMap;
            levelNamesMap = newLevelNamesMap;
            levelIDsMap = newLevelIDsMap;
            levelsList = newLevelsList;
        }
    }
    
    private Level(int _priority, string _name) {
        _name = _name.Trim();
        StringBuilder sb = new StringBuilder(_name.Length);
        char c;
        for(int x = 0; x < _name.Length; x++) {
            c = _name[x];
            if (ACCEPTABLE_NAME_CHARACTERS.IndexOf(c) >= 0) {
                sb.Append(c);
            }
        }
        _name = sb.ToString();
        if (_name.Equals(BLANK_STRING)) {
            throw new ApplicationException("Invalid name");
        }

        Priority = _priority;
        PriorityString = _priority.ToString();
        Name = _name;
        NameLowerCase = Name.ToLower();
        NameUpperCase = Name.ToUpper();
        IsPredefined = false;
        while (levelIDsMap.ContainsKey(nextID)) {
            nextID++;
        }
        id = nextID++;
        idString = id.ToString();
    }
    private Level(int _priority, string _name, int _id) {
        _name = _name.Trim();
        StringBuilder sb = new StringBuilder(_name.Length);
        char c;
        for(int x = 0; x < _name.Length; x++) {
            c = _name[x];
            if (ACCEPTABLE_NAME_CHARACTERS.IndexOf(c) >= 0) {
                sb.Append(c);
            }
        }
        _name = sb.ToString();
        if (_name.Equals(BLANK_STRING)) {
            throw new ApplicationException("Invalid name");
        }

        Priority = _priority;
        PriorityString = _priority.ToString();
        Name = _name;
        NameLowerCase = Name.ToLower();
        NameUpperCase = Name.ToUpper();
        IsPredefined = false;
        id = _id;
        idString = id.ToString();
    }
    
    private Level(int _id, int _priority, string _name, string _nameLowerCase, string _nameUpperCase, bool _isPredefined) {
        if (_name == null) {
            throw new ArgumentNullException();
        }
        _name = _name.Trim();
        if (_name.Equals(BLANK_STRING)) {
            throw new ApplicationException("Invalid name");
        }
        
        id = _id;
        idString = ID.ToString();
        Priority = _priority;
        PriorityString = _priority.ToString();
        Name = _name;
        NameLowerCase = _nameLowerCase;
        NameUpperCase = _nameUpperCase;
        IsPredefined = _isPredefined;
        
        AddLevel(this);
    }
    
    public override string ToString() {
        return Name;
    }
    
    /// <summary>
    /// Compares two Level objects by priority
    /// </summary>
    /// <param name="o">The level to which to compare this level</param>
    public int CompareTo(object o) {
        return (this.Priority - ((Level)o).Priority);
    }
    
    /// <summary>
    /// Compares two Level instances based on their integer priority
    /// </summary>
    public static bool operator <(Level _level1, Level _level2) {
        return _level1.Priority < _level2.Priority;
    }

    /// <summary>
    /// Compares two Level instances based on their integer priority
    /// </summary>
    public static bool operator <=(Level _level1, Level _level2) {
        return _level1.Priority <= _level2.Priority;
    }

    /// <summary>
    /// Compares two Level instances based on their integer priority
    /// </summary>
    public static bool operator >(Level _level1, Level _level2) {
        return _level1.Priority > _level2.Priority;
    }

    /// <summary>
    /// Compares two Level instances based on their integer priority
    /// </summary>
    public static bool operator >=(Level _level1, Level _level2) {
        return _level1.Priority >= _level2.Priority;
    }
    
}

}
